namespace EyeOpen.Data.DataAnnotations.Nhibernate
{
	using System;
	using System.Collections.Generic;
	using System.ComponentModel.DataAnnotations;
	using System.Data;
	using System.Linq;
	using System.Reflection;

	using EyeOpen.Linq;

	using NHibernate.Mapping.ByCode;

	internal class TypeAnnotationsMapper<T>
		where T : class
	{
		private readonly IDictionary<PropertyInfo, object[]> propertyAttributesDictionary;
		private readonly IEnumerable<PropertyInfo> referenceProperties;
		private readonly IEnumerable<PropertyInfo> collectionProperties;
		private string keyProperty;

		public TypeAnnotationsMapper()
		{
			var type = typeof(T);

			propertyAttributesDictionary =
				type
					.GetDataAnnotationsAttributes();

			referenceProperties =
				type
					.GetReferenceProperties();

			collectionProperties =
				type
					.GetCollectionProperties();
		}

		public TypeAnnotationsMapper<T> MapKey(IClassMapper<T> annotationsMap)
		{
			var keyPropertyAttribute =
				propertyAttributesDictionary
					.SingleOrDefault(x => x.Value.Any<KeyAttribute>());

			CheckKeyAttributeIsValid(typeof(T), keyPropertyAttribute);

			keyProperty = keyPropertyAttribute.Key.Name;

			annotationsMap.Id(keyProperty, m => m.Generator(Generators.Assigned));
			return this;
		}

		public TypeAnnotationsMapper<T> MapProperties(IPropertyContainerMapper<T> annotationsMap)
		{
			new PrimitivePropertyMapper<T>(annotationsMap, keyProperty, propertyAttributesDictionary)
				.Map();

			referenceProperties
				.Any()
				.Execute(() => new ReferenceMapper<T>(annotationsMap, referenceProperties).Map());

			collectionProperties
				.Any()
				.Execute(() => new CollectionMapper<T>(annotationsMap, collectionProperties).Map());

			return this;
		}

		/// <exception cref="MappingException">The key property cannot declares other attributes.</exception>
		private void CheckKeyAttributeIsValid(Type entityType, KeyValuePair<PropertyInfo, object[]> keyPropertyAttributes)
		{
			if (keyPropertyAttributes.Key == null)
			{
				throw new 
					MappingException(
						string.Format("Entity: {0}. The key property is not defined.", 
						entityType.FullName));
			}

			if (keyPropertyAttributes.Value.Count() != 1)
			{
				throw new MappingException(
					string.Format("Entity: {{0}}. The key property cannot d{0}eclares other attributes.", 
					entityType.FullName));
			}
		}
	}
}